/**
 * @func 上报数据函数 
 */

/* 
扩展
    xhr请求是异步发送，很可能在它即将发送的时候，页面已经卸载了，从而导致发送取消或者发送失败。异步请求响应返回后，由于页面和相关资源已经卸载，会引起function not found的错误,
    sendBeacon方法，发出的是异步请求，但是请求是作为浏览器任务执行的，与当前页面是脱钩的。因此该方法不会阻塞页面卸载流程和延迟后面页面的加载
    img打点上报：
        1、支持度高，不存在兼容性问题
        2、onError可以处理发送失败的回调进行重试。

*/
import { isBrowserEnv } from '../utils/tools'
import { _support } from '../utils/global'
import { options } from './options';
import { behaviors } from './behaviorCollection'
export class TransportData {
    queue: Queue = new Queue();//消息队列 目的让消息有序进行发送
    constructor() { }
    // 上报数据
    async send(data) {
        const url = options.dsn
        if (!url) {
            console.error('you-see: dsn为空，没有传入监控错误上报的dsn地址，请在init中传入');
            return;
        }
        // const result = await this._beforePost(data)
        const result = this._getTransportData(data)
        if (isBrowserEnv && result) {
            // 优先使用sendBeacon 上报，若数据量大，再使用图片打点上报和fetch上报
            const value = this._requestReportData('beaconReport', result)
            if (!value) {
                return options.useImgUpload ?
                    this._requestReportData('imgReport', result) :
                    this._requestReportData('xhrReport', result);
            }
        }

    }
    async _beforePost(this: any, data: any): Promise<any | boolean> {
        let transportData = this._getTransportData(data);
        // 配置了beforeDataReport
        if (typeof options.beforeDataReport === 'function') {
            transportData = options.beforeDataReport(transportData);
        }
        return transportData;
    }
    // 上报数据格式化，添加公共信息
    _getTransportData(data) {
        // 获取用户ID  可能是自定义的方法
        let usetId = options.userId || ''
        if (options.getUserId && typeof options.getUserId === 'function') {
            const id = options.getUserId();
            if (typeof id === 'string' || typeof id === 'number') {
                usetId = id;
            }
        }

        let res = {
            ...data,
            userId: usetId,
            sdkVersion: '1.0.1',
            apikey: options.apikey,
            uuid: options.uuid,
            deviceInfo: _support.deviceInfo, // 获取设备信息
            pageUrl: document.location.href,//上报地址
            breadcrumb: behaviors.getTask(),// 获取用户行为栈
        }
        return res
    }

    /**
   * @func 数据上报
   * @params type:上报方式
   * @params url:上报地址
   * @params data:上报数据
   * */
    _requestReportData(type, data) {
        let url = options.dsn
        let obj = {
            'beaconReport': () => {
                return window.navigator.sendBeacon(url, JSON.stringify(data))
            },
            'imgReport': () => {
                const requestfunc = () => {
                    const img = new Image();
                    let spliceStr = url.indexOf('?') === -1 ? '?' : '&';
                    img.src = `${url}${spliceStr}data=${encodeURIComponent(JSON.stringify(data))}`;
                }
                this.queue.addFun(requestfunc)
            },
            'xhrReport': () => {
                const requestfunc = () => {
                    fetch(url, {
                        method: 'POST',
                        body: JSON.stringify(data),
                        headers: {
                            'Content-Type': 'application/json',
                        },
                    });
                }
                this.queue.addFun(requestfunc)
            }
        }
        return obj[type]()

    }

}


export class Queue {
    private stack: any[] = [];
    private isFlushing = false;
    constructor() { }
    addFun(fn) {
        if (typeof fn != 'function') return
        this.stack.push(fn);
        // 判断环境是否支持promise请求
        // 放到任务队列里面，保证有序性
        if (!this.isFlushing) {
            this.isFlushing = true
            Promise.resolve().then(() => { this.flushStack() })
        }
    }
    clear() {
        this.stack = [];
    }
    getStack() {
        return this.stack;
    }
    flushStack() {
        let temp = this.stack.slice(0)
        this.stack = []
        this.isFlushing = false
        for (let i = 0; i < temp.length; i++) {
            temp[i]()
        }
    }
}

const transportData = _support.transportData || (_support.transportData = new TransportData());
export { transportData };